﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ExcelAddIn1
{
    /**
     * Implementation of the Spearman rank correlation coefficient test.
     */
    class Test_SpearmanRankCorrelation : Test
    {
        private class RankItem : IComparable
        {
            public int index;
            public double value;
            public double rank;
        
            public RankItem(int index, double value)
            {
                this.index = index;
                this.value = value;
                this.rank = 0;
            }
        
            public int CompareTo(Object obj)
            {
                RankItem ri = (RankItem)obj;
                if (value < ri.value)
                {
                    return -1;
                }
                else if (value > ri.value)
                {
                    return 1;
                }
                else
                {
                    return 0;
                }
            }
        }

        private double[] SRCcrit010 = { 0.900, 0.829, 0.714, 0.643, 0.600, 0.564, 0.536, 0.497 };
        private double[] SRCcrit005 = { -1, 0.886, 0.786, 0.738, 0.700, 0.648, 0.618, 0.591 };
    
        public Test_SpearmanRankCorrelation(DataContainer data)
        {
            this.data = data;
            res = new List<String>();
        }

        public override bool EnableAlphaField()
        {
            return false;
        }

        public override string GetInfo()
        {
            String s = "Calculates the Spearman rank correlation coefficient between two datasets. ";
            s += "Does not require that the datasets are normally distributed.";
            return s;
        }

        public override void RunTest()
        {
            res.Add("Spearman rank correlation coefficient");
            
            //Error check
            if (data.GetNoSets() < 2)
            {
                res.Add("At least two datasets is required!");
                return;
            }

            for (int i1 = 0; i1 < data.GetNoSets(); i1++)
            {
                for (int i2 = 0; i2 < data.GetNoSets(); i2++)
                {
                    if (i1 != i2 && i1 < i2)
                    {
                        RunTest(i1, i2);
                    }
                }
            }
        }

        public void RunTest(int i1, int i2)
        {
            DataSet d1 = data.GetDataSet(i1);
            DataSet d2 = data.GetDataSet(i2);

            int n = Math.Min(d1.GetN(), d2.GetN());

            res.Add(";" + d1.GetName() + ";" + d2.GetName() + ";-");
            res.Add("N;" + d1.GetN() + ";" + d2.GetN() + ";-");
            res.Add("Mean;" + d1.GetMean().ToString("F2") + ";" + d2.GetMean().ToString("F2"));
            res.Add("StDev;" + d1.GetStDev().ToString("F2") + ";" + d2.GetStDev().ToString("F2"));

            //Error check 1
            if (n < 5 || n > 12)
            {
                res.Add("Sample size must be between 5 and 12");
                return;
            }

            //Create ranking tables and find the squared rank difference
            List<RankItem> v1 = CreateRankingsTable(d1);
            List<RankItem> v2 = CreateRankingsTable(d2);

            double sumD2 = 0.0;
            for (int i = 0; i < n; i++)
            {
                RankItem rt1 = GetItem(v1, i);
                RankItem rt2 = GetItem(v2, i);

                double d = rt1.rank - rt2.rank;
                sumD2 += Math.Pow(d, 2);
            }

            //Calculate R-score
            double R = Math.Abs(1.0 - 6.0 * sumD2 / (double)(n * (Math.Pow(n, 2) - 1)));

            //Find Critical R
            int DF = n - 5;
            double Rc = -1.0;
            if (alpha == 0.05) Rc = SRCcrit005[DF];
            if (alpha == 0.10) Rc = SRCcrit010[DF];

            //Error check 2
            if (Rc <= 0.0)
            {
                res.Add("No valid critical R found for n=" + n);
                return;
            }

            res.Add("α;" + alpha + ";;-");
            res.Add("R-score;" + R.ToString("F2"));
            res.Add("R-crit;" + Rc.ToString("F2"));
            if (R < Rc)
            {
                res.Add("Result;No significant correlation");
            }
            else
            {
                res.Add("Result;Significant correlation");   
            }
            res.Add(";;;-");
        }

        private RankItem GetItem(List<RankItem> rTab, int index)
        {
            foreach (RankItem rt in rTab)
            {
                if (rt.index == index)
                {
                    return rt;
                }
            }
            return null;
        }

        private List<RankItem> CreateRankingsTable(DataSet ds)
        {
            //Create the rankings table
            List<RankItem> rTab = new List<RankItem>(ds.GetN());
            for (int i = 0; i < ds.GetN(); i++)
            {
                rTab.Add(new RankItem(i, ds.GetValue(i)));
            }

            //Sort the table
            rTab.Sort();
            //Fill in ranks
            for (int i = 0; i < ds.GetN(); i++)
            {
                rTab[i].rank = i + 1;
            }
            //Fix rank duplicates
            double prev = rTab[0].value;
            double sumRank = rTab[0].rank;
            int cnt = 1;
            for (int i = 1; i < ds.GetN(); i++)
            {
                double iVal = rTab[i].value;
                if (iVal == prev)
                {
                    //Equal values. Find a mean rank.
                    sumRank += rTab[i].rank;
                    cnt++;
                }
                else
                {
                    if (cnt > 1)
                    {
                        //Duplicates found. Update rank to a mean rank.
                        double meanR = sumRank / (double)(cnt);
                        for (int d = i - 1; d >= i - cnt; d--)
                        {
                            rTab[d].rank = meanR;
                        }
                    }
                    cnt = 1;
                    sumRank = rTab[i].rank;
                }
                prev = iVal;
            }
            return rTab;
        }
    }
}
